package com.mobbanana.gamehook

import android.app.Activity
import android.app.AndroidAppHelper
import android.app.Application
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Environment
import androidx.core.app.ActivityCompat
import com.mobbanana.gamehook.utils.DexUtil
import com.mobbanana.gamehook.utils.JDexUtil
import com.mobbanana.gamehook.utils.JReflectUtil
import com.mobbanana.gamehook.utils.ReflectUtil
import com.mobbanana.gamehook.utils.ReflectUtil.exemptAll
import dalvik.system.DexClassLoader
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage
import java.util.*

class UnityMonitor : IXposedHookLoadPackage {

    private var mPackageName: String? = null
    val unitySender: String = "com.unity3d.player.UnityPlayer"
    val unityReflectCls: String = "com.unity3d.player.ReflectionHelper"
    val javaUtil: String = "java.util.Locale"
    val unityReflectionMethodName = "getMethodID"
    val sdk1: String = "com.kuaiyouxi.gamepad.sdk.shell.assist.SDKAssist"
    val sdk2: String = "com.mobbanana.host.MobAssist"
    val sdkTestMainCls = "com.mobbanana.sdktest.SDKTest"

    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) {
        mPackageName = "com.mobbanana."
        val packageName2 = "air.com.gameever.juanitoarcade"
        val packageName3 = "com.lawson.tower"


        var packageName = lpparam!!.packageName
        if (packageName.startsWith(mPackageName!!) || packageName.startsWith(packageName2!!) || packageName3.equals(packageName)) {
            XposedBridge.log("Start hook packageName: $mPackageName")
            fixMultiDex(lpparam)
            var result = JReflectUtil.exemptAll()
            XposedBridge.log("ExemptAll result: $result")
            dynamicLoad()
        }
    }

    private fun fixMultiDex(lpparam: XC_LoadPackage.LoadPackageParam) {
        XposedHelpers.findAndHookMethod(Application::class.java, "attach", Context::class.java, object : XC_MethodHook() {
            override fun afterHookedMethod(param: MethodHookParam?) {
                super.afterHookedMethod(param)
                var context = param!!.args[0] as Context
                var classLoader = context.classLoader
                XposedBridge.log(classLoader.toString())

                var hookCls: Class<*>? = null
                try {
                    hookCls = classLoader.loadClass(unitySender)
                    getUnitySendMessage(hookCls)
                    hookCls = classLoader.loadClass(unityReflectCls)
                    getUnityCallJavaInfo(hookCls, unityReflectionMethodName)
                    hookCls = classLoader.loadClass(javaUtil)
//                    hookSystemLanguage(hookCls)
                } catch (e: Exception) {
                    XposedBridge.log(e.message + " 可能是MultiDex的问题!")
                    return
                }


            }

            override fun beforeHookedMethod(param: MethodHookParam?) {
                super.beforeHookedMethod(param)
            }
        })
    }

    private fun getUnityCallJavaInfo(clz: Class<*>, methodName: String) {
        XposedHelpers.findAndHookMethod(clz, methodName, Class::class.java, String::class.java, String::class.java, Boolean::class.java, object : XC_MethodHook() {
            override fun afterHookedMethod(param: MethodHookParam?) {
                super.afterHookedMethod(param)
                XposedBridge.log("afterHookedMethod")
                val args = param!!.args
                val args2 = arrayOf(args[2])
                XposedBridge.log("CallJava method: \n*** jClass: ${args[0]} ***\n *** methodName: ${args[1]} ***\n  *** args: ${args[2]} ***\n  *** isStatic: ${args[3]} ***\n")
            }
        })
    }

    private fun getUnitySendMessage(clz: Class<*>) {
        XposedHelpers.findAndHookMethod(clz, "UnitySendMessage", String::class.java, String::class.java, String::class.java, object : XC_MethodHook() {
            override fun afterHookedMethod(param: MethodHookParam?) {
                super.afterHookedMethod(param)
                if (param!!.args[0] == "java.lang.Class") {
                    return
                }
                XposedBridge.log("afterHookedMethod")
                for (i in param.args.indices) {
                    XposedBridge.log("Send to Unity method: ${param.method.name} arg:$i ->  value:${param.args[i]}")
                }
            }
        })
    }

    private fun loadDex(context: Context, dexPath: String) {
        try {
            XposedBridge.log("Start load dex -> $dexPath")
            var dexLoader = DexClassLoader(dexPath, context.cacheDir.path, context.cacheDir.path, context.classLoader)
            var cls = dexLoader.loadClass(sdkTestMainCls)
            var methodInit = cls.getMethod("init", Context::class.java);
            methodInit.invoke(null, context)
        } catch (ex: java.lang.Exception) {
            XposedBridge.log(ex)
        }
    }

    private fun loadSo(context: Context, soPath: String) {
        val version = Build.VERSION.SDK_INT
        if (version >= 28) {
            XposedHelpers.callMethod(Runtime.getRuntime(), "nativeLoad", soPath, context.classLoader)
        } else {
            XposedHelpers.callMethod(Runtime.getRuntime(), "doLoad", soPath, context.classLoader)
        }
    }

    private fun canReadSDCard(context: Context): Boolean {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            return context.checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
        } else {
            XposedBridge.log("No permission to read sdcard!")
        }
        return false;
    }

    private fun requestPermission(activity: Activity) {
        XposedBridge.log("Start request permission with xposed!")
        val permissions: kotlin.Array<String> = arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
        ActivityCompat.requestPermissions(activity, permissions, 110)
    }

    private fun onRequestPermissionResult() {
        XposedHelpers.findAndHookMethod(Activity::class.java, "onRequestPermissionsResult", Int::class.java, kotlin.Array<String>::class.java, IntArray::class.java, object : XC_MethodHook() {
            override fun beforeHookedMethod(param: MethodHookParam?) {
                super.beforeHookedMethod(param)
            }

            override fun afterHookedMethod(param: MethodHookParam?) {
                super.afterHookedMethod(param)
                var requestCode: Int = param!!.args[0] as Int
                if (requestCode == 110) {
                    var permissions: kotlin.Array<String> = param.args[1] as kotlin.Array<String>
                    val resultCodes: IntArray = param.args[2] as IntArray
                    for (i in permissions.indices) {
                        XposedBridge.log("Permission: " + permissions[i] + " Result:" + resultCodes[i])
                        if (permissions[i].equals(android.Manifest.permission.READ_EXTERNAL_STORAGE)&&resultCodes[i]==0){
                            hookMobSDK(param.thisObject as Context)
                        }
                    }
                }
            }
        })
    }

    private fun hookMobSDK(context: Context) {
        var sdk1Cls = XposedHelpers.findClassIfExists(sdk1, context.classLoader)
        var sdk2Cls = XposedHelpers.findClassIfExists(sdk2, context.classLoader)
        if (sdk1Cls == null && sdk2Cls == null) {
            loadSo(context, Environment.getExternalStorageDirectory().path + "/libxlib.so");
            JDexUtil.LoadDex(context, Environment.getExternalStorageDirectory().path + "/sdk-test.dex")
        }
    }


    private fun dynamicLoad() {
        XposedHelpers.findAndHookMethod(Activity::class.java, "onCreate", Bundle::class.java, object : XC_MethodHook() {
            override fun beforeHookedMethod(param: MethodHookParam?) {
                super.beforeHookedMethod(param)
            }

            override fun afterHookedMethod(param: MethodHookParam?) {
                super.afterHookedMethod(param)
                var activity: Activity = param!!.thisObject as Activity
                XposedBridge.log("Activity class name: " + activity.javaClass.name)
                if (!canReadSDCard(activity)) {
                    onRequestPermissionResult()
                    requestPermission(activity)
                }else{
                    hookMobSDK(param.thisObject as Context)
                }
            }
        })
    }

    private fun hookSystemLanguage(clz: Class<*>) {
        XposedHelpers.findAndHookMethod(clz, "getDefault", object : XC_MethodHook() {
            override fun afterHookedMethod(param: MethodHookParam?) {
                super.afterHookedMethod(param)
                var local: Locale = param!!.result as Locale
                XposedBridge.log(" local :${local}")
                XposedBridge.log("afterHookedMethod")
                val l = Locale("zh_CN")
                param.result = l
//                XposedBridge.log(Log.getStackTraceString(IllegalArgumentException()))
//                for (i in param.args.indices) {
//                    XposedBridge.log("""On method: ${param.method.name} arg:$i -> " value:${param.args[i]}""")
//                }
            }
        })
    }
}